﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using ObjectTransposeApi.Handler.Expand;
using ObjectTransposeApi.Structure;
using System.Collections;
using ObjectTransposeApi.Handler.Property;
using ObjectTransposeApi.Extension;
using ObjectTransposeApi.Helper;

namespace ObjectTransposeApi
{
    /// <summary/>
    public static class ObjectToList
    {
        /// <summary/>
        public static List<T> Copy<T>(
            object input,
            params AbstractMapper[] mappers) where T : new()
        {
            PropertyMapper[] propertyMappers = Array.FindAll(mappers, value => typeof(PropertyMapper).IsInstanceOfType(value)).Select(value => (PropertyMapper)value).ToArray();
            IndexMapper[] indexMappers = Array.FindAll(mappers, value => typeof(IndexMapper).IsInstanceOfType(value)).Select(value => (IndexMapper)value).ToArray();
            TypeMapper[] typeMappers = Array.FindAll(mappers, value => typeof(TypeMapper).IsInstanceOfType(value)).Select(value => (TypeMapper)value).ToArray();

            AbstractExpandHandler expandHandler = ChainHelper.FetchExpandHandlers();
            AbstractPropertyHandler propertyHandler = ChainHelper.FetchPropertyHandler();

            List<T> destinations = new List<T>();

            // the fist thing that needs to be is expand the input variable ... if it is a collection then
            // each item in the collection needs to tested for a match against the destination object
            foreach (object source in expandHandler.Process(input))
            {
                Queue<QueueStructure> queueStructures = new Queue<QueueStructure>();

                T destination = new T();

                // add the items .. the getter property on the destination is needed to handle collections
                queueStructures.Enqueue(new QueueStructure(
                    destination,
                    ReflectionHelper.FetchGetSetProperties(destination.GetType()),
                    source,
                    ReflectionHelper.FetchGetProperties(source.GetType())));

                // process each queued item
                while (queueStructures.Count > 0)
                {
                    QueueStructure queueStructure = queueStructures.Dequeue();

                    // enum the destination properties
                    foreach (PropertyInfo destinationPropertyInfo in queueStructure.DestinationPropertyInfos)
                    {
                        // find a matching source property
                        PropertyInfo sourcePropertyInfo = ReflectionHelper.FetchMatchingProperty(
                            queueStructure.SourcePropertyInfos,
                            destinationPropertyInfo.Name,
                            propertyMappers);

                        // if a matching source property is not found then move on
                        if (sourcePropertyInfo == null)
                        {
                            continue;
                        }

                        IndexMapper indexMapper = Array.Find(indexMappers, value => value.SourceName == sourcePropertyInfo.Name);

                        // build the structure needed by the handler
                        PropertyStructure propertyStructure = new PropertyStructure()
                        {
                            ExpandCallback = expandHandler.Process,
                            Destination = queueStructure.Destination,
                            DestinationPropertyInfo = destinationPropertyInfo,
                            Source = queueStructure.Source,
                            SourcePropertyInfo = sourcePropertyInfo,
                            IndexMapper = indexMapper,
                            TypeMappers = typeMappers,
                        };

                        // process the structure and add new items if any
                        queueStructures.EnqueueRange(propertyHandler.Process(propertyStructure));
                    }
                }
                
                // add the item
                destinations.Add(destination);
            }

            // return the object
            return destinations;
        }
    }
}
